using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace ProjectCastamere
{
    class PlacementGrid
    {
        float sizeX, sizeZ;
        float posX, posZ;
        float elevation;
        GridCell[,] cells;
        int numCellsX, numCellsZ;

        struct Coord
        {
            public int x, y;
        }
        private List<Coord> startPoints = new List<Coord>();
        private Coord endPoint;


        public PlacementGrid(ContentManager manager, DataTypes.MapFile file)
        {
            // load the image file
            Texture2D grid = manager.Load<Texture2D>(file.PlacementGridFile);

            this.numCellsX = grid.Width;
            this.numCellsZ = grid.Height;

            // determines whether the placement grid is valid after the image
            // processing step
            bool hasStartPoint = false;
            bool hasEndPoint = false;

            cells = new GridCell[grid.Width, grid.Height];
            // process the grid file to determine start and end points
            Color[] colors = new Color[grid.Width * grid.Height];
            grid.GetData<Color>(colors, 0, grid.Width * grid.Height);
            for (int x = 0; x < grid.Width; ++x)
            {
                for (int y = 0; y < grid.Height; ++y)
                {
                    Color c = colors[y * grid.Width + x];
                    // if the cell is blue
                    if (c == Color.Blue)
                    {
                        // represents an enemy starting point
                        Coord sp = new Coord();
                        sp.x = x;
                        sp.y = y;
                        startPoints.Add(sp);
                        // towers can't be placed on starting points of enemies
                        cells[x, y].TowersCanBePlaced = false;
                        hasStartPoint = true;
                    }
                    // if teh cell is red
                    else if (c == Color.Red)
                    {
                        // represents the core location (Enemy end point)
                        endPoint.x = x;
                        endPoint.y = y;
                        cells[x, y].TowersCanBePlaced = true;
                        hasEndPoint = true;
                    }
                    else if (c == Color.Black)
                    {
                        // represents cells which towers cant be placed nor enemies can cross
                        cells[x, y].TowersCanBePlaced = false;
                    }
                    else if (c == Color.White)
                    {
                        cells[x, y].TowersCanBePlaced = true;
                    }
                }
            }

            if (!hasStartPoint || !hasEndPoint)
            {
                // throw exception - map data invalid
                throw new Exception("Placement grid image does not contain a start or end point (blue, red resp.)");
            }

            this.sizeX = file.GridSizeX;
            this.sizeZ = file.GridSizeZ;
            this.posX = file.GridCenterPosX;
            this.posZ = file.GridCenterPosZ;
            this.elevation = file.GridElevation;
        }

        /// <summary>
        /// the data returned from a ray-trace into the placement grid, used
        /// for when the player is wanting to place towers on certain cells of the grid
        /// </summary>
        public struct TraceResult
        {
            public int cellX, cellZ;
            public Vector3 poi;             // point of intersection
            public bool validIntersection;
        }

        public TraceResult traceRay(Vector3 a, Vector3 b)
        {
            TraceResult r = new TraceResult();
            if (a.Y > elevation && b.Y < elevation)
            {
                // calculate the point of intersection
                float alen = (a.Y - elevation);
                float length = (elevation - b.Y) + alen;

                float ratio = alen / length;

                r.poi = a + (b - a) * ratio;

                // now calculate the grid cell based on the point of intersection
                // first calculate relative to the centre location of the placement grid
                float relx = r.poi.X - this.posX;
                float relz = r.poi.Z - this.posZ;

                // add half-size and divide by cell size to get the grid indices
                relx += (sizeX * 0.5f);
                relz += (sizeZ * 0.5f);

                relx /= (sizeX / (float)numCellsX);
                relz /= (sizeZ / (float)numCellsZ);

                // cast to int
                r.cellX = (int)relx;
                r.cellZ = (int)relz;

                if (r.cellX < 0 || r.cellX >= numCellsX ||
                    r.cellZ < 0 || r.cellZ >= numCellsZ)
                {
                    r.validIntersection = false;
                }
                r.validIntersection = true;
            }
            else
            {
                r.validIntersection = false;
            }

            return r;
        }

        private class AStarNode
        {
            public int F, G, H;
            public AStarNode parent;
            public Coord coord = new Coord();
        }


        private bool IsInRange(int x, int z)
        {
            return x >= 0 && x < numCellsX && z >= 0 && z < numCellsZ;
        }

        /// <summary>
        /// calculates a random path between one of the start points and the end point, returns
        /// null if a path could not be found
        /// </summary>
        /// <param name="startX"></param>
        /// <param name="startY"></param>
        /// <param name="endX"></param>
        /// <param name="endY"></param>
        /// <returns></returns>
        public Path CalculateRandomPath()
        {
            Path path = new Path();
            // choose a random starting location
            Random r = new Random();
            int startloc = 3;// r.Next(startPoints.Count - 1);

            int startx = startPoints[startloc].x;
            int startz = startPoints[startloc].y;

            // allocate temp nodes array for calculating path
            AStarNode[,] nodes = new AStarNode[numCellsX, numCellsZ];
            for(int x = 0; x < numCellsX; ++x)
                for (int y = 0; y < numCellsZ; ++y)
                {
                    nodes[x, y] = new AStarNode();
                    nodes[x, y].coord.x = x;
                    nodes[x, y].coord.y = y;
                }


            // use a-star to find a valid path
            List<AStarNode> openList = new List<AStarNode>();
            List<AStarNode> closedList = new List<AStarNode>();

            // add the start node to begin
            openList.Add(nodes[startx, startz]);

            // while the closed list does not contain the destination node
            do
            {
                // if the open list is empty it means a valid path cannot be found
                if (openList.Count == 0)
                {
                    return null;
                }

                AStarNode current = openList.First();
                int lowestF = current.F;

                // find the node with the lowest f-cost in the open-list
                foreach (AStarNode node in openList)
                {
                    if (node.F < lowestF)
                    {
                        lowestF = node.F;
                        current = node;
                    }
                }

                //Console.WriteLine("processing node[" + current.coord.x + ", " + current.coord.y + "]");

                // add the current node to the closed-list and
                // remove it from the open list
                openList.Remove(current);
                closedList.Add(current);

                // used to calculate the G movement cost
                int[,] gtable = new int[3, 3] {
                { -1, 10, -1 },
                { 10, -1, 10 },
                { -1, 10, -1 }};

                // for each adjacent node to the current one
                for (int x = -1; x < 2; ++x)
                {
                    for (int y = -1; y < 2; ++y)
                    {
                        if (gtable[x + 1, y + 1] < 0)
                        {
                            continue;
                        }

                        // get the nodes coord
                        int nx = current.coord.x + x;
                        int ny = current.coord.y + y;



                        // check if the [nx,ny] node is either not valid to walk
                        // on or if it is in the closed list
                        if (!IsInRange(nx, ny) ||
                            !cells[nx, ny].IsWalkable() ||
                            closedList.Contains(nodes[nx, ny]))
                        {
                            // ignore
                            continue;
                        }

                        AStarNode n = nodes[nx, ny];

                        // if its not on the opened list
                        if (!openList.Contains(n))
                        {
                            // add it to the open list and set its parent
                            openList.Add(n);
                            n.parent = current;

                            // calculate F, G and H for this square
                            n.G = current.G + gtable[x + 1, y + 1];
                            // use manhattan distance heuristic between current point and end point
                            n.H = Math.Abs(n.coord.x - endPoint.x) + Math.Abs(n.coord.y - endPoint.y);

                            n.F = n.G + n.H;
                        }
                        else
                        {
                            // if it is on the open-list, check to see if this path is better
                            // i.e. lower G-cost
                            int tmpG = current.G + gtable[x + 1, y + 1];
                            if (tmpG < n.G)
                            {
                                n.G = tmpG;
                                n.F = n.G + n.H;
                                n.parent = current;
                            }
                        }
                    }
                }
            }
            while (!closedList.Contains(nodes[endPoint.x, endPoint.y]));

            Matrix transform = Matrix.Identity;
                   // Matrix.CreateTranslation(new Vector3(0.5f, 0.0f, 0.5f)) *
                   // Matrix.CreateScale(new Vector3(sizeX * 0.5f, 1.0f, sizeZ * 0.5f)) *
                  //  Matrix.CreateTranslation(new Vector3(posX, elevation, posZ));

            // work backwards - from the end node to the start
            for (AStarNode n = nodes[endPoint.x, endPoint.y]; n != nodes[startx, startz]; n = n.parent)
            {
                path.AddPointReverse(Vector3.Transform(new Vector3(n.coord.x, 0.0f, n.coord.y), transform));
            }

            // return the path
            return path;
        }


        /////////////////////////////////////////////////////////////////////
        // for debugging mainly...
        public Vector3[] GetQuad(int gridX, int gridZ)
        {
            if (!IsInRange(gridX, gridZ))
            {
                return null;
            }

            Vector3[] verts = new Vector3[4];

            float cellSizeX = sizeX / (float)numCellsX;
            float cellSizeZ = sizeZ / (float)numCellsZ;


            float left = (gridX * cellSizeX) - (sizeX*0.5f);
            float bottom = (gridZ * cellSizeZ) - (sizeZ*0.5f);
            verts[0].X = left;
            verts[1].X = left + cellSizeX;
            verts[2].X = left + cellSizeX;
            verts[3].X = left;

            verts[0].Z = bottom;
            verts[1].Z = bottom;
            verts[2].Z = bottom + cellSizeZ;
            verts[3].Z = bottom + cellSizeZ;

            return verts;
        }

        private static void DrawQuad(GraphicsDevice device, Vector3[] verts)
        {
            VertexPositionNormalTexture[] v = new VertexPositionNormalTexture[4];
            for (int i = 0; i < 4; ++i)
            {
                v[i].Position = verts[i];
            }

            int[] indices = new int[6] { 0, 1, 2, 0, 2, 3 };
            device.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, v, 0, 4, indices, 0, 2);
        }



        public void Draw(Camera camera, GraphicsDevice device, Path path)
        {
            RasterizerState rs = new RasterizerState();
            rs.FillMode = FillMode.WireFrame;
            device.RasterizerState = rs;

            BasicEffect effect = new BasicEffect(device);
            effect.EnableDefaultLighting();
            effect.World = Matrix.Identity;
            effect.View = camera.ViewMatrix;
            effect.Projection = camera.ProjectionMatrix;

            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Apply();
                // get all the valid tower placement points
                for (int x = 0; x < numCellsX; ++x)
                {
                    for (int z = 0; z < numCellsZ; ++z)
                    {
                        if (cells[x, z].TowersCanBePlaced)
                        {
                            // draw the cell
                            DrawQuad(device, GetQuad(x, z));
                        }
                    }
                }
            }

            rs = new RasterizerState();
            rs.FillMode = FillMode.Solid;
            device.RasterizerState = rs;

            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Apply();
                // get all path points
                for (int i = 0; i < path.GetPointCount(); ++i)
                {
                    Vector3 v = path.GetPoint(i);
                    DrawQuad(device, GetQuad((int)v.X, (int)v.Z));
                }
            }
        }
        ////////////////////////////////////////////////////////////////////////
    }
}
